package edu.whu.rose.clock.kps.algorithm.blinks2.index;

import java.io.UnsupportedEncodingException;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;

import com.sleepycat.je.DatabaseException;

import edu.whu.rose.clock.ssql.util.SequentialIntArray;

public class KeywordBlockCache {

	private HashMap<String, SequentialIntArray> cache;  // smaller than 50000
	private HashMap<String, SequentialIntArray> cache2; // smaller than 100000
	private HashMap<String, SequentialIntArray> cache3; // smaller than 500000
	private HashMap<String, SequentialIntArray> cache4; // smaller than 1000000
	private HashMap<String, SequentialIntArray> cache5; // larger than 1000000
	
	private long capacity;
	private BilevelIndexDB2 bidb;
	private HashSet<String> stored;
	private long size;
	
	private HashSet<String> leastVisited;           // visited once
	private HashSet<String> leastVisited2;          // visited twice
	private HashSet<String> leastVisited3;          // visited three times
	private HashSet<String> leastVisited4;          // visited four times
	private HashSet<String> leastVisited5;          // visited five times
	
	private long visit;
	private long hit;
	
	private long totalnum;
	
	public KeywordBlockCache(BilevelIndexDB2 bidb, long capacity) {
		cache = new HashMap<String, SequentialIntArray>();
		cache2 = new HashMap<String, SequentialIntArray>();
		cache3 = new HashMap<String, SequentialIntArray>();
		cache4 = new HashMap<String, SequentialIntArray>();
		cache5 = new HashMap<String, SequentialIntArray>();
		
		this.bidb = bidb;
		this.capacity = capacity;
		this.stored = new HashSet<String>();
		
		leastVisited = new HashSet<String>();
		leastVisited2 = new HashSet<String>();
		leastVisited3 = new HashSet<String>();
		leastVisited4 = new HashSet<String>();
		leastVisited5 = new HashSet<String>();
	}
	
	public void insert(String lcKey, int block) throws DatabaseException, UnsupportedEncodingException {
//		if (lcKey.equals("helge")) {
//			System.out.println();
//		}
		SequentialIntArray data = cache.get(lcKey);
		if (data != null) {
			data.insert(block);
			size += 4;
			if (data.getCapacity() > 8) {
				cache2.put(lcKey, data);
				cache.remove(lcKey);
			}
			if (leastVisited.contains(lcKey)) {
				leastVisited.remove(lcKey);
				leastVisited2.add(lcKey);
			}
			else if (leastVisited2.contains(lcKey)) {
				leastVisited2.remove(lcKey);
				leastVisited3.add(lcKey);
			}
			else if (leastVisited3.contains(lcKey)) {
				leastVisited3.remove(lcKey);
				leastVisited4.add(lcKey);
			}
			else if (leastVisited4.contains(lcKey)) {
				leastVisited4.remove(lcKey);
				leastVisited5.add(lcKey);
			}
			else if (leastVisited5.contains(lcKey)) {
				leastVisited5.remove(lcKey);
				size -= 2 * lcKey.length();
			}
			visit++;
			hit++;
		}
		else {
			data = cache2.get(lcKey);
			if (data != null) {
				data.insert(block);
				size += 4;
				if (data.getCapacity() > 16) {
					cache3.put(lcKey, data);
					cache2.remove(lcKey);
				}
				if (leastVisited.contains(lcKey)) {
					leastVisited.remove(lcKey);
					leastVisited2.add(lcKey);
				}
				else if (leastVisited2.contains(lcKey)) {
					leastVisited2.remove(lcKey);
					leastVisited3.add(lcKey);
				}
				else if (leastVisited3.contains(lcKey)) {
					leastVisited3.remove(lcKey);
					leastVisited4.add(lcKey);
				}
				else if (leastVisited4.contains(lcKey)) {
					leastVisited4.remove(lcKey);
					leastVisited5.add(lcKey);
				}
				else if (leastVisited5.contains(lcKey)) {
					leastVisited5.remove(lcKey);
					size -= 2 * lcKey.length();
				}
				visit++;
				hit++;
			}
			else {
				data = cache3.get(lcKey);
				if (data != null) {
					data.insert(block);
					size += 4;
					if (data.getCapacity() > 32) {
						cache4.put(lcKey, data);
						cache3.remove(lcKey);
					}
					if (leastVisited.contains(lcKey)) {
						leastVisited.remove(lcKey);
						leastVisited2.add(lcKey);
					}
					else if (leastVisited2.contains(lcKey)) {
						leastVisited2.remove(lcKey);
						leastVisited3.add(lcKey);
					}
					else if (leastVisited3.contains(lcKey)) {
						leastVisited3.remove(lcKey);
						leastVisited4.add(lcKey);
					}
					else if (leastVisited4.contains(lcKey)) {
						leastVisited4.remove(lcKey);
						leastVisited5.add(lcKey);
					}
					else if (leastVisited5.contains(lcKey)) {
						leastVisited5.remove(lcKey);
						size -= 2 * lcKey.length();
					}
					visit++;
					hit++;
				}
				else {
					data = cache4.get(lcKey);
					if (data != null) {
						data.insert(block);
						size += 4;
						if (data.getCapacity() > 64) {
							cache5.put(lcKey, data);
							cache4.remove(lcKey);
						}
						if (leastVisited.contains(lcKey)) {
							leastVisited.remove(lcKey);
							leastVisited2.add(lcKey);
						}
						else if (leastVisited2.contains(lcKey)) {
							leastVisited2.remove(lcKey);
							leastVisited3.add(lcKey);
						}
						else if (leastVisited3.contains(lcKey)) {
							leastVisited3.remove(lcKey);
							leastVisited4.add(lcKey);
						}
						else if (leastVisited4.contains(lcKey)) {
							leastVisited4.remove(lcKey);
							leastVisited5.add(lcKey);
						}
						else if (leastVisited5.contains(lcKey)) {
							leastVisited5.remove(lcKey);
							size -= 2 * lcKey.length();
						}
						visit++;
						hit++;
					}
					else {
						data = cache5.get(lcKey);
						if (data != null) {
							data.insert(block);
							size += 4;
							if (leastVisited.contains(lcKey)) {
								leastVisited.remove(lcKey);
								leastVisited2.add(lcKey);
							}
							else if (leastVisited2.contains(lcKey)) {
								leastVisited2.remove(lcKey);
								leastVisited3.add(lcKey);
							}
							else if (leastVisited3.contains(lcKey)) {
								leastVisited3.remove(lcKey);
								leastVisited4.add(lcKey);
							}
							else if (leastVisited4.contains(lcKey)) {
								leastVisited4.remove(lcKey);
								leastVisited5.add(lcKey);
							}
							else if (leastVisited5.contains(lcKey)) {
								leastVisited5.remove(lcKey);
								size -= 2 * lcKey.length();
							}
							visit++;
							hit++;
						}
						else {
							long least = 2 * lcKey.length() + 4;
							String s = null;
							while (size + least > capacity) {
								if (leastVisited.size() > 0) {
									Iterator<String> it = leastVisited.iterator();
									s = it.next();
									it.remove();
									size -= 2 * s.length();
								}
								else {
									if (leastVisited2.size() > 0) {
										Iterator<String> it2 = leastVisited2.iterator();
										s = it2.next();
										it2.remove();
										size -= 2 * s.length();
									}
									else {
										if (leastVisited3.size() > 0) {
											Iterator<String> it3 = leastVisited3.iterator();
											s = it3.next();
											it3.remove();
											size -= 2 * s.length();
										}
										else {
											if (leastVisited4.size() > 0) {
												Iterator<String> it4 = leastVisited4.iterator();
												s = it4.next();
												it4.remove();
												size -= 2 * s.length();
											}
											else {
												if (leastVisited5.size() > 0) {
													Iterator<String> it5 = leastVisited5.iterator();
													s = it5.next();
													it5.remove();
													size -= 2 * s.length();
												}
												else {
													if (cache.size() > 0) {
														Iterator<String> it6 = cache.keySet().iterator();
														s = it6.next();
													}
													else if (cache2.size() > 0) {
														Iterator<String> it6 = cache2.keySet().iterator();
														s = it6.next();
													}
													else if (cache3.size() > 0) {
														Iterator<String> it6 = cache3.keySet().iterator();
														s = it6.next();
													}
													else if (cache4.size() > 0) {
														Iterator<String> it6 = cache4.keySet().iterator();
														s = it6.next();
													}
													else {
														Iterator<String> it6 = cache5.keySet().iterator();
														while (it6.hasNext()) {
															s = it6.next();
//															SequentialIntArray gii = cache5.get(s);
//															if (gii.getSize() < 128) {
//																break;
//															}
														}
													}
												}
											}
										}
									}
								}
								remove(s);
							}
							
							if (stored.contains(lcKey)) {
								int[] temp = getFromDB(lcKey);
								SequentialIntArray eia = new SequentialIntArray(temp.length);
								for (int i = 0; i < temp.length; i++) {
									eia.insert(temp[i]);
								}
								data = eia;
								stored.remove(lcKey);
								if (data != null) {
									data.insert(block);
									if (data.getCapacity() < 8) {
										cache.put(lcKey, data);
									}
									else if (data.getCapacity() < 16) {
										cache2.put(lcKey, data);
									}
									else if (data.getCapacity() < 32) {
										cache3.put(lcKey, data);
									}
									else if (data.getCapacity() < 64) {
										cache4.put(lcKey, data);
									}
									else {
										cache5.put(lcKey, data);
									}
									
									size += 2 * lcKey.length() + 4 * data.getCapacity();
									visit++;
								}
								else {
									System.err.println("Error");
								}
							}
							else {
								totalnum++;
								data = new SequentialIntArray(8);
								data.insert(block);
								cache.put(lcKey, data);
								size += 2 * lcKey.length() + data.getCapacity();
								leastVisited.add(lcKey);
								size += 2 * lcKey.length();
							}
						}
					}
				}
			}
		}
	}
	
	private void remove(String keyword) throws UnsupportedEncodingException, DatabaseException {
		System.out.println(keyword);
		SequentialIntArray gii = cache.remove(keyword);
		if (gii != null) {
			size -= 4 * gii.getCapacity();
			putIntoDB(keyword, gii);
			stored.add(keyword);
		}
		else {
			gii = cache2.remove(keyword);
			if (gii != null) {
				size -= 4 * gii.getCapacity();
				putIntoDB(keyword, gii);
				stored.add(keyword);
			}
			else {
				gii = cache3.remove(keyword);
				if (gii != null) {
					size -= 4 * gii.getCapacity();
					putIntoDB(keyword, gii);
					stored.add(keyword);
				}
				else {
					gii = cache4.remove(keyword);
					if (gii != null) {
						size -= 4 * gii.getCapacity();
						putIntoDB(keyword, gii);
						stored.add(keyword);
					}
					else {
						gii = cache5.remove(keyword);
						if (gii != null) {
							size -= 4 * gii.getCapacity();
							putIntoDB(keyword, gii);
							stored.add(keyword);
						}
						else {
							System.err.println("error");
						}
					}
				}
			}
		}
	}
	
	public int[] getFromDB(String key) throws UnsupportedEncodingException, DatabaseException {
		int[] result = bidb.getFromKeywordBlock(key);
		return result;
	}
	
	public void putIntoDB(String key, SequentialIntArray data) throws UnsupportedEncodingException, DatabaseException {
		bidb.insertToKeywordBlock(key, data.getArray());
	}
	
	public void flush() throws UnsupportedEncodingException, DatabaseException {
		Iterator<String> it = cache.keySet().iterator();
		int counter = 0;
		Date stime = new Date();
		Date etime = null;
		while (it.hasNext()) {
			String key = it.next();
			SequentialIntArray data = cache.get(key);
			this.putIntoDB(key, data);
			counter++;
			if (counter % 1000 == 0) {
				etime = new Date();
				System.out.println(counter + "........................." + (etime.getTime() - stime.getTime()) + "ms");
				stime = etime;
			}
		}
		it = cache2.keySet().iterator();
		while (it.hasNext()) {
			String key = it.next();
			SequentialIntArray data = cache2.get(key);
//			if (data == null) {
//				System.out.println();
//			}
			this.putIntoDB(key, data);
			counter++;
			if (counter % 1000 == 0) {
				etime = new Date();
				System.out.println(counter + "........................." + (etime.getTime() - stime.getTime()) + "ms");
				stime = etime;
			}
		}
		it = cache3.keySet().iterator();
		while (it.hasNext()) {
			String key = it.next();
			SequentialIntArray data = cache3.get(key);
			this.putIntoDB(key, data);
			counter++;
			if (counter % 1000 == 0) {
				etime = new Date();
				System.out.println(counter + "........................." + (etime.getTime() - stime.getTime()) + "ms");
				stime = etime;
			}
		}
		it = cache4.keySet().iterator();
		while (it.hasNext()) {
			String key = it.next();
			SequentialIntArray data = cache4.get(key);
			this.putIntoDB(key, data);
			counter++;
			if (counter % 1000 == 0) {
				etime = new Date();
				System.out.println(counter + "........................." + (etime.getTime() - stime.getTime()) + "ms");
				stime = etime;
			}
		}
		it = cache5.keySet().iterator();
		while (it.hasNext()) {
			String key = it.next();
			SequentialIntArray data = cache5.get(key);
			this.putIntoDB(key, data);
			counter++;
			if (counter % 1000 == 0) {
				etime = new Date();
				System.out.println(counter + "........................." + (etime.getTime() - stime.getTime()) + "ms");
				stime = etime;
			}
		}
		System.out.println("Total number of keywords flushed: " + counter);
	}
	
	public void statistic() {
		System.out.println("Total number of keywords inserted: " + totalnum);
		System.out.println("Cache Hit Rate: " + (100 * (double)hit / ((double)visit)) + "%");
	}
}
